Attribute VB_Name = "NPCs"
Option Explicit

Public Sub NPC_Emote(ByVal NPCIndex As Integer, ByVal EmoID As Byte)

'*****************************************************************
'NPC uses an emoticon
'*****************************************************************
    
    'Check the last time an emoticon was used
    If NPCList(NPCIndex).Counters.EmoDelay > timeGetTime Then Exit Sub
    
    'Set the delay
    NPCList(NPCIndex).Counters.EmoDelay = timeGetTime + 5000
    
    'Send the packet
    ConBuf.PreAllocate 4
    ConBuf.Put_Byte DataCode.User_Emote
    ConBuf.Put_Byte EmoID
    ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
    Data_Send ToNPCArea, NPCIndex, ConBuf.Get_Buffer(), NPCList(NPCIndex).Pos.Map

End Sub

Public Sub NPC_Push(ByVal NPCIndex As Integer, ByVal Direction As Byte, ByVal MaxTiles As Byte)

'*****************************************************************
'Pushes a NPC in a certain direction up to MaxTiles tiles
'*****************************************************************
Dim OldHeading As Byte
Dim CharIndex As Integer
Dim Dir2 As Byte
Dim Dir3 As Byte
Dim i As Long

    'Confirm a valid NPC
    If NPCIndex > LastNPC Then Exit Sub
    If NPCIndex < 1 Then Exit Sub
    If NPCList(NPCIndex).Flags.NPCAlive = 0 Then Exit Sub
    
    'Check if the NPC is being grabbed
    If NPCList(NPCIndex).Flags.GrabbedBy > 0 Then
        CharIndex = NPCList(NPCIndex).Flags.GrabbedBy

        'Confirm the one grabbing the NPC is actually grabbing them
        Select Case CharList(CharIndex).CharType
            Case CharType_PC
            
                'For some reason, the grabbing is only bound one way, so release it
                If UserList(CharList(CharIndex).Index).Flags.GrabChar <> NPCList(NPCIndex).Char.CharIndex Then
                    Skill_Grab_PCtoNPC_Release CharList(CharIndex).Index, NPCIndex
                End If
                
        End Select
        
        'The NPC is being grabbed, so no move for you!
        If NPCList(NPCIndex).Flags.GrabbedBy > 0 Then Exit Sub
    
    End If

    'Store the NPC's old heading
    OldHeading = NPCList(NPCIndex).Char.Heading

    'Find alternate positions (the two directions that surround the selected direction)
    Select Case Direction
        Case NORTH
            Dir2 = NORTHEAST
            Dir3 = NORTHWEST
        Case EAST
            Dir2 = NORTHEAST
            Dir3 = SOUTHEAST
        Case SOUTH
            Dir2 = SOUTHWEST
            Dir3 = SOUTHEAST
        Case WEST
            Dir2 = SOUTHWEST
            Dir3 = NORTHWEST
        Case NORTHEAST
            Dir2 = NORTH
            Dir3 = EAST
        Case SOUTHEAST
            Dir2 = EAST
            Dir3 = SOUTH
        Case SOUTHWEST
            Dir2 = SOUTH
            Dir3 = WEST
        Case NORTHWEST
            Dir2 = WEST
            Dir3 = NORTH
    End Select
    
    'Loop through the amount of tiles
    For i = 1 To MaxTiles
        
        'Try to move the primary direction
        If NPC_MoveChar(NPCIndex, Direction, False) = 0 Then
            
            'Move in secondary directions
            If NPC_MoveChar(NPCIndex, Dir2, False) = 0 Then
                If NPC_MoveChar(NPCIndex, Dir3, False) = 0 Then
                    
                    'We can't move anymore
                    Exit For
                    
                End If
            End If
    
        End If
    
    Next i
    
    'Restore the old heading
    NPCList(NPCIndex).Char.Heading = OldHeading
    
    'Send the position update
    ConBuf.PreAllocate 6
    ConBuf.Put_Byte DataCode.Server_WarpChar
    ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
    ConBuf.Put_Byte NPCList(NPCIndex).Pos.X
    ConBuf.Put_Byte NPCList(NPCIndex).Pos.Y
    ConBuf.Put_Byte NPCList(NPCIndex).Char.Heading Or 128
    Data_Send ToMap, 0, ConBuf.Get_Buffer(), NPCList(NPCIndex).Pos.Map

End Sub

Public Sub NPC_UpdateModStats(ByVal NPCIndex As Integer)

'*****************************************************************
'Updates the NPC's mod stats
'*****************************************************************

    Log "Call NPC_UpdateModStats(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\

    With NPCList(NPCIndex)
    
        'Copy the base stats to the mod stats (we can use copymemory since we dont have to give item bonuses)
        CopyMemory .ModStat(FirstModStat), .BaseStat(FirstModStat), ((NumStats - FirstModStat) + 1) * 4 '* 4 since we are using longs (4 bytes)
        
        'Modifications from primary stats
        .ModStat(SID.WeaponSkill) = .BaseStat(SID.ELV) + .ModStat(SID.WeaponSkill) + (.ModStat(SID.Str) + .ModStat(SID.Str) + .ModStat(SID.Agi) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Armor) = .BaseStat(SID.ELV) + .ModStat(SID.Armor) + (.ModStat(SID.Str) + .ModStat(SID.Str) + .ModStat(SID.Agi) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Accuracy) = .BaseStat(SID.ELV) + .ModStat(SID.Accuracy) + (.ModStat(SID.Dex) + .ModStat(SID.Dex) + .ModStat(SID.Agi) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Evade) = .BaseStat(SID.ELV) + .ModStat(SID.Evade) + (.ModStat(SID.Agi) + .ModStat(SID.Agi) + .ModStat(SID.Dex) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Perception) = .BaseStat(SID.ELV) + .ModStat(SID.Perception) + (.ModStat(SID.Dex) + .ModStat(SID.Inte) + .ModStat(SID.Inte) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Regen) = .BaseStat(SID.ELV) + .ModStat(SID.Regen) + (.ModStat(SID.Inte) + .ModStat(SID.Inte) + .ModStat(SID.Inte) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Recov) = .BaseStat(SID.ELV) + .ModStat(SID.Recov) + (.ModStat(SID.Str) + .ModStat(SID.Str) + .ModStat(SID.Agi) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Tactics) = .BaseStat(SID.ELV) + .ModStat(SID.Tactics) + (.ModStat(SID.Agi) + .ModStat(SID.Agi) + .ModStat(SID.Dex) + .ModStat(SID.Brave)) \ 5
        .ModStat(SID.Immunity) = .BaseStat(SID.ELV) + .ModStat(SID.Immunity) + (.ModStat(SID.Inte) + .ModStat(SID.Inte) + .ModStat(SID.Str) + .ModStat(SID.Brave)) \ 5

        'Update the EP/HP
        .ModStat(SID.MaxEP) = (.BaseStat(SID.ELV) * 10) + .ModStat(SID.MaxEP) + .ModStat(SID.Inte) * 3
        .ModStat(SID.MaxHP) = (.BaseStat(SID.ELV) * 10) + .ModStat(SID.MaxHP) + .ModStat(SID.Str) * 3

        'Min/max hit (damage) modification
        .ModStat(SID.MinHIT) = .ModStat(SID.MinHIT) + (.ModStat(SID.WeaponSkill) \ 2) + (.ModStat(SID.Str) \ 3) + (.ModStat(SID.Agi) \ 4)
        .ModStat(SID.MaxHIT) = .ModStat(SID.MaxHIT) + (.ModStat(SID.WeaponSkill) \ 2) + (.ModStat(SID.Str) \ 3) + (.ModStat(SID.Agi) \ 4)
        
        'Defense modificaitons
        .ModStat(SID.DEF) = .ModStat(SID.DEF) + (.ModStat(SID.Armor) \ 2) + (.ModStat(SID.Str) \ 3) + (.ModStat(SID.Agi) \ 4)
        
        '*** Skills ***
        
        'Grab skill
        If NPCList(NPCIndex).Flags.GrabbedBy > 0 Then
            .ModStat(SID.Evade) = .ModStat(SID.Evade) \ 1.33
        End If
        
        'Crack armor
        If NPCList(NPCIndex).ActiveSkills.CrackArmorTime > 0 Then
            .ModStat(SID.DEF) = .ModStat(SID.DEF) \ 2
        End If
        
    End With

End Sub

Private Function NPC_AI_Attack(ByVal NPCIndex As Integer) As Byte

'*****************************************************************
'Calls the NPC attack AI - only call by the NPC_AI routine!
'*****************************************************************
Dim HeadingLoop As Long
Dim nPos As WorldPos
Dim Damage As Long
Dim X As Long

    Log "Call NPC_AI_Attack(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\
    
    'Clear the replenish value
    NPCList(NPCIndex).Counters.ReplenishCounter = timeGetTime

    '*** Melee attacking ***
    If NPCList(NPCIndex).AttackRange <= 1 Then
        
        'Check in all directions
        For HeadingLoop = NORTH To NORTHWEST
            nPos = NPCList(NPCIndex).Pos
            Server_HeadToPos HeadingLoop, nPos
            
            'If a legal pos and a user is found attack
            If nPos.X > 0 Then
                If nPos.Y > 0 Then
                    If nPos.X <= MapInfo(nPos.Map).Width Then
                        If nPos.Y <= MapInfo(nPos.Map).Height Then
                            If MapInfo(nPos.Map).Data(nPos.X, nPos.Y).UserIndex > 0 Then
                                X = MapInfo(nPos.Map).Data(nPos.X, nPos.Y).UserIndex
                            
                                'Get the damage
                                Damage = NPC_AttackUser(NPCIndex, X)
                
                                'Send the attack packet
                                ConBuf.PreAllocate 12
                                ConBuf.Put_Byte DataCode.Combo_SlashSoundRotateDamage
                                ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
                                ConBuf.Put_Integer UserList(X).Char.CharIndex
                                ConBuf.Put_Long NPCList(NPCIndex).AttackGrh
                                ConBuf.Put_Byte NPCList(NPCIndex).AttackSfx
                                If Damage > 32000 Then ConBuf.Put_Integer 32000 Else ConBuf.Put_Integer Damage
                                Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map
                
                                'Apply damage
                                NPC_AttackUser_ApplyDamage NPCIndex, X, Damage
                                NPC_AI_Attack = 1
                                Exit Function
                            End If
                        End If
                    End If
                End If
            End If
            
        Next HeadingLoop
        
    '*** Ranged attacking ***
    Else
        
        'Check for the closest user
        X = NPC_AI_ClosestPC(NPCIndex, NPCList(NPCIndex).AttackRange \ 2, NPCList(NPCIndex).AttackRange \ 2)
        If X > 0 Then
            
            'Check for a valid range
            If Server_Distance(NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y, UserList(X).Pos.X, UserList(X).Pos.Y) <= NPCList(NPCIndex).AttackRange Then

                'Check for a valid path
                If Engine_ClearPath(NPCList(NPCIndex).Pos.Map, NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y, UserList(X).Pos.X, UserList(X).Pos.Y) Then
                    
                    'Get the damage
                    Damage = NPC_AttackUser(NPCIndex, X)

                    'Face the NPC to the target
                    ConBuf.PreAllocate 13
                    ConBuf.Put_Byte DataCode.Combo_ProjectileSoundRotateDamage
                    ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
                    ConBuf.Put_Integer UserList(X).Char.CharIndex
                    ConBuf.Put_Long NPCList(NPCIndex).AttackGrh
                    ConBuf.Put_Byte NPCList(NPCIndex).ProjectileRotateSpeed
                    ConBuf.Put_Byte NPCList(NPCIndex).AttackSfx
                    If Damage > 32000 Then ConBuf.Put_Integer 32000 Else ConBuf.Put_Integer Damage
                    Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map

                    'Apply damage
                    NPC_AttackUser_ApplyDamage NPCIndex, X, Damage
                    NPC_AI_Attack = 1
                    Exit Function
                
                End If
            End If
        End If
    End If

End Function

Private Function NPC_AI_AttackNPC(ByVal NPCIndex As Integer, Optional ByVal NotSlaveOfUserIndex As Integer = 0) As Byte

'*****************************************************************
'Calls the NPC attack AI to attack another NPC - only call by the NPC_AI routine!
'*****************************************************************
Dim Damage As Long
Dim HeadingLoop As Long
Dim nPos As WorldPos
Dim X As Long

    Log "Call NPC_AI_AttackNPC(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\

    '*** Melee attacking ***
    If NPCList(NPCIndex).AttackRange <= 1 Then
        
        'Check in all directions
        For HeadingLoop = NORTH To NORTHWEST
            nPos = NPCList(NPCIndex).Pos
            Server_HeadToPos HeadingLoop, nPos

            'If a legal pos and a NPC is found attack
            If nPos.X > 0 Then
                If nPos.Y > 0 Then
                    If nPos.X <= MapInfo(nPos.Map).Width Then
                        If nPos.Y <= MapInfo(nPos.Map).Height Then
                            If MapInfo(nPos.Map).Data(nPos.X, nPos.Y).NPCIndex > 0 Then
                                X = MapInfo(nPos.Map).Data(nPos.X, nPos.Y).NPCIndex
                                If NPCList(X).Attackable Then
                                    If NPCList(X).Hostile Then
                                        If NPCList(X).OwnerIndex <> NotSlaveOfUserIndex Then
                                        
                                            'Get the damage
                                            Damage = NPC_AttackNPC(NPCIndex, X)
                            
                                            'Send the attack packet
                                            ConBuf.PreAllocate 12
                                            ConBuf.Put_Byte DataCode.Combo_SlashSoundRotateDamage
                                            ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
                                            ConBuf.Put_Integer NPCList(X).Char.CharIndex
                                            ConBuf.Put_Long NPCList(NPCIndex).AttackGrh
                                            ConBuf.Put_Byte NPCList(NPCIndex).AttackSfx
                                            If Damage > 32000 Then ConBuf.Put_Integer 32000 Else ConBuf.Put_Integer Damage
                                            Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map
                            
                                            'Apply damage
                                            NPC_Damage X, 0, Damage, NPCList(NPCIndex).Char.CharIndex
                                            NPC_AI_AttackNPC = 1
                                            Exit Function
                                        
                                        End If
                                    End If
                                End If
                            End If
                        End If
                    End If
                End If
            End If
            
        Next HeadingLoop
        
    '*** Ranged attacking ***
    Else
        
        'Check for the closest npc to attack
        X = NPC_AI_ClosestNPC(NPCIndex, NPCList(NPCIndex).AttackRange \ 2, NPCList(NPCIndex).AttackRange \ 2, NotSlaveOfUserIndex)
        If X > 0 Then
            
            'Check for a valid range
            If Server_Distance(NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y, NPCList(X).Pos.X, NPCList(X).Pos.Y) <= NPCList(NPCIndex).AttackRange Then

                'Check for a valid path
                If Engine_ClearPath(NPCList(NPCIndex).Pos.Map, NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y, NPCList(X).Pos.X, NPCList(X).Pos.Y) Then
    
                    'Get the damage
                    Damage = NPC_AttackNPC(NPCIndex, X)

                    'Face the NPC to the target
                    ConBuf.PreAllocate 13
                    ConBuf.Put_Byte DataCode.Combo_ProjectileSoundRotateDamage
                    ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
                    ConBuf.Put_Integer NPCList(X).Char.CharIndex
                    ConBuf.Put_Long NPCList(NPCIndex).AttackGrh
                    ConBuf.Put_Byte NPCList(NPCIndex).ProjectileRotateSpeed
                    ConBuf.Put_Byte NPCList(NPCIndex).AttackSfx
                    If Damage > 32000 Then ConBuf.Put_Integer 32000 Else ConBuf.Put_Integer Damage
                    Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map

                    'Apply damage
                    NPC_Damage X, 0, Damage, NPCList(NPCIndex).Char.CharIndex
                    NPC_AI_AttackNPC = 1
                    Exit Function
                
                End If
            End If
        End If
        
    End If

End Function

Private Sub NPC_AI_2_Random(ByVal NPCIndex As Integer)

'*****************************************************************
'AI 2 - Random movement (will attack if hostile, but not chase)
'*****************************************************************
Dim b As Byte

    'Attack
    If NPCList(NPCIndex).Hostile Then b = NPC_AI_Attack(NPCIndex)
    If b = 1 Then
        NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + NPCList(NPCIndex).ModStat(SID.AttackDelay)
        Exit Sub
    End If
    
    'Move
    If Int(Rnd * 3) = 0 Then
        NPC_MoveChar NPCIndex, Int(Rnd * 8) + 1
    End If
    NPCList(NPCIndex).Counters.ActionDelay = NPCList(NPCIndex).Counters.ActionDelay + 500

End Sub

Private Sub NPC_AI_3_MeleeChaser(ByVal NPCIndex As Integer)

'*****************************************************************
'AI 3 - Chases enemies, attacks them melee-style
'*****************************************************************
Dim tHeading As Byte
Dim b As Byte
Dim t1 As Byte
Dim t2 As Byte
Dim X As Long

    'Attack
    If NPCList(NPCIndex).Hostile Then b = NPC_AI_Attack(NPCIndex)
    If b = 0 Then
        If NPCList(NPCIndex).Hostile Then b = NPC_AI_AttackNPC(NPCIndex)
    End If
    If b = 1 Then
        NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + NPCList(NPCIndex).ModStat(SID.AttackDelay)
        Exit Sub
    End If
    
    'Look for a user
    X = NPC_AI_ClosestPC(NPCIndex, (ScreenWidth \ 64) - 1, (ScreenHeight \ 64) - 1)
    
    'If no users are nearby, then put a moderate delay to lighten the CPU load
    If X = 0 Then
        NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + 1000
        NPCList(NPCIndex).Flags.IsIdling = 1
        Exit Sub
    
    Else
    
        'Yummy, food! Make food emoticon
        If NPCList(NPCIndex).Flags.WasIdling Then
            If Int(Rnd * 3) = 0 Then
                Select Case UCase$(NPCList(NPCIndex).Name)
                    Case "ZOMBIE"
                        NPC_Emote NPCIndex, EmoID.Meat
                End Select
            End If
        End If
    
        'Find the direction to move
        tHeading = Server_FindDirection(NPCList(NPCIndex).Pos, UserList(X).Pos)
        
        'Move towards the retrieved position
        If NPC_MoveChar(NPCIndex, tHeading) = 0 Then
        
            'Move towards alternate positions (the two directions that surround the selected direction)
            Select Case tHeading
                Case NORTH
                    t1 = NORTHEAST
                    t2 = NORTHWEST
                Case EAST
                    t1 = NORTHEAST
                    t2 = SOUTHEAST
                Case SOUTH
                    t1 = SOUTHWEST
                    t2 = SOUTHEAST
                Case WEST
                    t1 = SOUTHWEST
                    t2 = NORTHWEST
                Case NORTHEAST
                    t1 = NORTH
                    t2 = EAST
                Case SOUTHEAST
                    t1 = EAST
                    t2 = SOUTH
                Case SOUTHWEST
                    t1 = SOUTH
                    t2 = WEST
                Case NORTHWEST
                    t1 = WEST
                    t2 = NORTH
            End Select
            
            'Do the alternate movement
            If NPC_MoveChar(NPCIndex, t1) = 0 Then
                If NPC_MoveChar(NPCIndex, t2) = 0 Then
                    NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + 1000   'Nowhere to go, so wait a while to try again
                    NPCList(NPCIndex).Flags.IsIdling = 1
                End If
            End If
        
        End If
            
        Exit Sub

    End If

End Sub

Public Sub NPC_AI(ByVal NPCIndex As Integer)

'*****************************************************************
'Moves NPC based on it's .movement value
'*****************************************************************
Dim tHeading As Byte
Dim t1 As Byte
Dim t2 As Byte
Dim t3 As Byte
Dim t4 As Byte
Dim Y As Long
Dim X As Long
Dim b As Byte
Dim i As Integer
Dim tX As Long
Dim tY As Long
Dim tPos As WorldPos
Dim CharType As Byte

    Log "Call NPC_AI(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\
    
    'Confirm the NPC is still in a valid condition to perform the AI
    If NPCList(NPCIndex).Flags.NPCActive = 0 Then Exit Sub
    If NPCList(NPCIndex).Flags.NPCAlive = 0 Then Exit Sub
    If NPCList(NPCIndex).Pos.Map <= 0 Then Exit Sub
    If NPCList(NPCIndex).Pos.Map > NumMaps Then Exit Sub
    If NPCList(NPCIndex).Pos.X <= 0 Then Exit Sub
    If NPCList(NPCIndex).Pos.Y <= 0 Then Exit Sub
    If NPCList(NPCIndex).Pos.X > MapInfo(NPCList(NPCIndex).Pos.Map).Width Then Exit Sub
    If NPCList(NPCIndex).Pos.Y > MapInfo(NPCList(NPCIndex).Pos.Map).Height Then Exit Sub
    
    'Check if the NPC is stunned
    If NPCList(NPCIndex).ActiveSkills.StunTime > timeGetTime Then Exit Sub
    
    'Remove the idling flag (if they are idling, it is set again in the AI routines)
    NPCList(NPCIndex).Flags.WasIdling = NPCList(NPCIndex).Flags.IsIdling
    NPCList(NPCIndex).Flags.IsIdling = 0

    'Find which AI sub to call
    Select Case NPCList(NPCIndex).AI
        Case 2: NPC_AI_2_Random NPCIndex
        Case 3: NPC_AI_3_MeleeChaser NPCIndex
        
        'Case 6:
            ' This NPC has no AI here - the only reference to the banker AI is in the clicking events.
            ' Just do a search for "ai = 6" to find it.

    End Select
    
    With NPCList(NPCIndex)
    
        'Check the full replenish time
        If .Counters.ReplenishCounter + NPCReplenishTime < timeGetTime Then
            If .BaseStat(SID.MinHP) < .BaseStat(SID.MaxHP) Then NPC_Heal NPCIndex, .BaseStat(SID.MaxHP) - .BaseStat(SID.MinHP)
            If .BaseStat(SID.MinEP) < .BaseStat(SID.MaxEP) Then NPC_ChangeEP NPCIndex, .BaseStat(SID.MaxEP) - .BaseStat(SID.MinEP)
            .Counters.ReplenishCounter = timeGetTime
        End If
        
        'Check the partial replenish time
        If RecoverNPCStats Then
            If .BaseStat(SID.MinHP) < .BaseStat(SID.MaxHP) Then NPC_Heal NPCIndex, 1 + (.ModStat(SID.Recov) \ 2)
            If .BaseStat(SID.MinEP) < .BaseStat(SID.MaxEP) Then NPC_ChangeEP NPCIndex, 1 + (.ModStat(SID.Regen) \ 2)
        End If
        
    End With

End Sub

Public Function NPC_AI_ClosestChar(ByVal NPCIndex As Integer, ByVal SearchX As Byte, ByVal SearchY As Byte, Optional ByVal NotSlaveOfUserIndex As Integer = 0, Optional ByVal IsAttackable As Byte = 1, Optional ByVal IsHostile As Byte = 1) As Integer

'*****************************************************************
'Return the index of the closest player character (NPC or PC)
'Note - the char index is returned, not PC or NPC index!
'*****************************************************************
Dim tX As Integer
Dim tY As Integer
Dim X As Integer
Dim Y As Integer

    Log "Call NPC_AI_ClosestChar(" & NPCIndex & "," & SearchX & "," & SearchY & ")", CodeTracker '//\\LOGLINE//\\
    
    'Check for a valid, active NPC
    If NPCList(NPCIndex).Flags.NPCAlive = 0 Then Exit Function
    If NPCList(NPCIndex).Flags.NPCActive = 0 Then Exit Function
    
    'Expand the search range
    For tX = 1 To SearchX
        For tY = 1 To SearchY
            'Loop through the search area (only look on the outside of the search rectangle to prevent checking the same thing multiple times)
            For X = NPCList(NPCIndex).Pos.X - tX To NPCList(NPCIndex).Pos.X + tX Step tX
                For Y = NPCList(NPCIndex).Pos.Y - tY To NPCList(NPCIndex).Pos.Y + tY Step tY
                    'Make sure tile is legal
                    If X >= 1 Then
                        If X <= MapInfo(NPCList(NPCIndex).Pos.Map).Width Then
                            If Y >= 1 Then
                                If Y <= MapInfo(NPCList(NPCIndex).Pos.Map).Height Then
                                
                                    'Look for a npc
                                    If MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex > 0 Then
                                        If MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex <> NPCIndex Then
 
                                            'Perform special checks
                                            If IsAttackable Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).Attackable = 0 Then GoTo NextChar
                                            End If
                                            If IsHostile Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).Hostile = 0 Then GoTo NextChar
                                            End If
                                            If NotSlaveOfUserIndex > -1 Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).OwnerIndex = NotSlaveOfUserIndex Then GoTo NextChar
                                            End If
   
                                            'Look for a NPC
                                            NPC_AI_ClosestChar = NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).Char.CharIndex
                                            Log "Rtrn NPC_AI_ClosestChar = " & NPC_AI_ClosestChar, CodeTracker '//\\LOGLINE//\\
                                            Exit Function
                                            
                                        End If
                                    
                                    'Look for a PC
                                    ElseIf MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).UserIndex > 0 Then
                                        NPC_AI_ClosestChar = UserList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).UserIndex).Char.CharIndex
                                        Log "Rtrn NPC_AI_ClosestChar = " & NPC_AI_ClosestChar, CodeTracker '//\\LOGLINE//\\
                                        Exit Function
                                    End If
                                    
                                End If
                            End If
                        End If
                    End If

NextChar:
                    
                Next Y
            Next X
        Next tY
    Next tX
    
    Log "Rtrn NPC_AI_ClosestChar = " & NPC_AI_ClosestChar, CodeTracker '//\\LOGLINE//\\

End Function

Public Function NPC_AI_ClosestPC(ByVal NPCIndex As Integer, ByVal SearchX As Byte, ByVal SearchY As Byte) As Integer

'*****************************************************************
'Return the index of the closest player character (PC)
'*****************************************************************
Dim tX As Integer
Dim tY As Integer
Dim X As Integer
Dim Y As Integer

    Log "Call NPC_AI_ClosestPC(" & NPCIndex & "," & SearchX & "," & SearchY & ")", CodeTracker '//\\LOGLINE//\\
    
    'Check for a valid, active NPC
    If NPCList(NPCIndex).Flags.NPCAlive = 0 Then Exit Function
    If NPCList(NPCIndex).Flags.NPCActive = 0 Then Exit Function
    
    'Expand the search range
    For tX = 1 To SearchX
        For tY = 1 To SearchY
            'Loop through the search area (only look on the outside of the search rectangle to prevent checking the same thing multiple times)
            For X = NPCList(NPCIndex).Pos.X - tX To NPCList(NPCIndex).Pos.X + tX Step tX
                For Y = NPCList(NPCIndex).Pos.Y - tY To NPCList(NPCIndex).Pos.Y + tY Step tY
                    'Make sure tile is legal
                    If X >= 1 Then
                        If X <= MapInfo(NPCList(NPCIndex).Pos.Map).Width Then
                            If Y >= 1 Then
                                If Y <= MapInfo(NPCList(NPCIndex).Pos.Map).Height Then
                                    'Look for a user
                                    If MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).UserIndex > 0 Then
                                        NPC_AI_ClosestPC = MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).UserIndex
                                        Log "Rtrn NPC_AI_ClosestPC = " & NPC_AI_ClosestPC, CodeTracker '//\\LOGLINE//\\
                                        Exit Function
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next Y
            Next X
        Next tY
    Next tX
    
    Log "Rtrn NPC_AI_ClosestPC = " & NPC_AI_ClosestPC, CodeTracker '//\\LOGLINE//\\

End Function

Public Function NPC_AI_ClosestNPC(ByVal NPCIndex As Integer, ByVal SearchX As Byte, ByVal SearchY As Byte, Optional ByVal NotSlaveOfUserIndex As Integer = -1, Optional ByVal IsAttackable As Byte = 1, Optional ByVal IsHostile As Byte = 1, Optional ByVal IsSlaveOfUserIndex As Integer = -1) As Integer
 
'*****************************************************************
'Return the index of the closest player character (PC)
'*****************************************************************
Dim tX As Integer
Dim tY As Integer
Dim X As Integer
Dim Y As Integer
 
    Log "Call NPC_AI_ClosestNPC(" & NPCIndex & "," & SearchX & "," & SearchY & ")", CodeTracker '//\\LOGLINE//\\
 
    'Expand the search range
    For tX = 1 To SearchX
        For tY = 1 To SearchY
 
            'Loop through the search area (only look on the outside of the search rectangle to prevent checking the same thing multiple times)
            For X = NPCList(NPCIndex).Pos.X - tX To NPCList(NPCIndex).Pos.X + tX Step tX
                For Y = NPCList(NPCIndex).Pos.Y - tY To NPCList(NPCIndex).Pos.Y + tY Step tY
 
                    'Make sure tile is legal
                    If X > 0 Then
                        If X <= MapInfo(NPCList(NPCIndex).Pos.Map).Width Then
                            If Y > 0 Then
                                If Y <= MapInfo(NPCList(NPCIndex).Pos.Map).Height Then
 
                                    'Look for a npc
                                    If MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex > 0 Then
                                        If MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex <> NPCIndex Then
 
                                            'Perform special checks
                                            If IsAttackable Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).Attackable = 0 Then GoTo NextNPC
                                            End If
                                            If IsHostile Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).Hostile = 0 Then GoTo NextNPC
                                            End If
                                            If NotSlaveOfUserIndex > -1 Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).OwnerIndex = NotSlaveOfUserIndex Then GoTo NextNPC
                                            End If
                                            If IsSlaveOfUserIndex > -1 Then
                                                If NPCList(MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex).OwnerIndex <> IsSlaveOfUserIndex Then GoTo NextNPC
                                            End If
 
                                            'We found our match!
                                            NPC_AI_ClosestNPC = MapInfo(NPCList(NPCIndex).Pos.Map).Data(X, Y).NPCIndex
                                            Log "Rtrn NPC_AI_ClosestNPC = " & NPC_AI_ClosestNPC, CodeTracker '//\\LOGLINE//\\
                                            Exit Function
 
                                        End If
                                    End If
 
                                End If
                            End If
                        End If
                    End If
 
NextNPC:
 
                Next Y
            Next X
        Next tY
    Next tX
 
    Log "Rtrn NPC_AI_ClosestNPC = " & NPC_AI_ClosestNPC, CodeTracker '//\\LOGLINE//\\
 
End Function

Public Function HitLanded(ByVal Accuracy As Long, ByVal WeaponSkill As Long, ByVal Tactics As Long, ByVal tEvade As Long, ByVal tArmor As Long, ByVal tTactics As Long) As Boolean

'*****************************************************************
'Returns the hitrate value
'*****************************************************************
Dim HitRate As Long

    HitRate = 50 + Accuracy + (WeaponSkill \ 4) + (Tactics * (Rnd * 0.4 + 0.4)) _
            - tEvade - (tArmor \ 4) - (tTactics * (Rnd * 0.4 + 0.4))
    HitLanded = (Rnd * 100 < HitRate)

End Function

Private Function NPC_AttackNPC(ByVal NPCIndex As Integer, ByVal TargetIndex As Integer) As Long

'*****************************************************************
'Have a NPC attack a NPC
'*****************************************************************
Dim Hit As Long

    Log "Call NPC_AttackNPC(" & NPCIndex & "," & TargetIndex & ")", CodeTracker '//\\LOGLINE//\\

    'Check for an action delay
    If NPCList(NPCIndex).Counters.ActionDelay > timeGetTime Then
        Log "NPC_AttackNPC: NPC action delay > timeGetTime - aborting", CodeTracker '//\\LOGLINE//\\
        Exit Function
    End If

    'Set the action delay
    NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + NPCList(NPCIndex).ModStat(SID.AttackDelay)

    'Calculate if they hit
    If Not HitLanded(NPCList(NPCIndex).ModStat(SID.Accuracy), NPCList(NPCIndex).ModStat(SID.WeaponSkill), NPCList(NPCIndex).ModStat(SID.Tactics), _
        NPCList(TargetIndex).ModStat(SID.Evade), NPCList(TargetIndex).ModStat(SID.Armor), NPCList(TargetIndex).ModStat(SID.Tactics)) Then
        Log "NPC_AttackNPC: NPC's attack missed", CodeTracker '//\\LOGLINE//\\
        ConBuf.PreAllocate 5
        ConBuf.Put_Byte DataCode.Server_SetCharDamage
        ConBuf.Put_Integer NPCList(TargetIndex).Char.CharIndex
        ConBuf.Put_Integer -1
        Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(TargetIndex).Pos.Map
        Exit Function
    End If

    'Calculate hit
    Hit = Server_RandomNumber(NPCList(NPCIndex).ModStat(SID.MinHIT), NPCList(NPCIndex).ModStat(SID.MaxHIT))
    Hit = Hit - (NPCList(TargetIndex).ModStat(SID.DEF) \ 2)
    If Hit < 1 Then Hit = 1
    Log "NPC_AttackNPC: Hit value = " & Hit, CodeTracker '//\\LOGLINE//\\

    'Return the value
    NPC_AttackNPC = Hit
    
End Function

Public Sub NPC_AttackUser_ApplyDamage(ByVal NPCIndex As Integer, ByVal UserIndex As Integer, ByVal Hit As Long)

'*****************************************************************
'Applies damage on a user
'*****************************************************************

    'Hit user
    UserList(UserIndex).Stats.BaseStat(SID.MinHP) = UserList(UserIndex).Stats.BaseStat(SID.MinHP) - Hit

    'User Die
    If UserList(UserIndex).Stats.BaseStat(SID.MinHP) <= 0 Then
        Log "NPC_AttackUser_ApplyDamage: NPC's attack killed user", CodeTracker '//\\LOGLINE//\\
    
        'Kill user
        ConBuf.PreAllocate 3 + Len(NPCList(NPCIndex).Name)
        ConBuf.Put_Byte DataCode.Server_Message
        ConBuf.Put_Byte 73
        ConBuf.Put_String NPCList(NPCIndex).Name
        Data_Send ToIndex, UserIndex, ConBuf.Get_Buffer
        User_Kill UserIndex
        
    End If

End Sub

Private Function NPC_AttackUser(ByVal NPCIndex As Integer, ByVal UserIndex As Integer) As Long

'*****************************************************************
'Have a NPC attack a User
'*****************************************************************
Dim Hit As Long

    Log "Call NPC_AttackUser(" & NPCIndex & "," & UserIndex & ")", CodeTracker '//\\LOGLINE//\\

    'Check for an action delay
    If NPCList(NPCIndex).Counters.ActionDelay > timeGetTime Then
        Log "NPC_AttackUser: NPC action delay > timeGetTime - aborting", CodeTracker '//\\LOGLINE//\\
        NPC_AttackUser = -1
        Exit Function
    End If
    
    'Don't allow if not logged in completely
    If UserList(UserIndex).Flags.UserLogged = 0 Then
        Log "NPC_AttackUser: User " & UserIndex & " (" & UserList(UserIndex).Name & ") not logged in - aborting", CodeTracker '//\\LOGLINE//\\
        NPC_AttackUser = -1
        Exit Function
    End If

    'Set the action delay
    NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + NPCList(NPCIndex).ModStat(SID.AttackDelay)
    
    'Calculate if they hit
    If Not HitLanded(NPCList(NPCIndex).ModStat(SID.Accuracy), NPCList(NPCIndex).ModStat(SID.WeaponSkill), NPCList(NPCIndex).ModStat(SID.Tactics), _
        UserList(UserIndex).Stats.ModStat(SID.Evade), UserList(UserIndex).Stats.ModStat(SID.Armor), UserList(UserIndex).Stats.ModStat(SID.Tactics)) Then
        Log "NPC_AttackUser: NPC's attack missed", CodeTracker '//\\LOGLINE//\\
        NPC_AttackUser = 0
        Exit Function
    End If
    
    'Remove hiding state
    User_UnHide UserIndex

    'Calculate hit
    Hit = Server_RandomNumber(NPCList(NPCIndex).ModStat(SID.MinHIT), NPCList(NPCIndex).ModStat(SID.MaxHIT))
    Hit = Hit - (UserList(UserIndex).Stats.ModStat(SID.DEF) \ 2)
    If Hit < 1 Then Hit = 1
    
    'User attacked counter
    UserList(UserIndex).Flags.LastAttacked = timeGetTime
    
    'Return the value
    NPC_AttackUser = Hit

End Function

Public Sub NPC_ChangeEP(ByVal NPCIndex As Integer, ByVal RaiseBy As Long)
 
'*****************************************************************
'Chage a NPC's EP - ONLY USE THIS SUB TO CHANGE NPC EP
'Make RaiseBy positive for adding EP, and negative to take EP
'*****************************************************************
Dim EPA As Byte
Dim EPB As Byte
 
    'Get the EP percentage before changing
    EPA = CByte((NPCList(NPCIndex).BaseStat(SID.MinEP) / NPCList(NPCIndex).ModStat(SID.MaxEP)) * 100)
 
    'Raise the MP
    NPCList(NPCIndex).BaseStat(SID.MinEP) = NPCList(NPCIndex).BaseStat(SID.MinEP) + RaiseBy
 
    'Don't go over the limit
    If NPCList(NPCIndex).BaseStat(SID.MinEP) > NPCList(NPCIndex).ModStat(SID.MaxEP) Then NPCList(NPCIndex).BaseStat(SID.MinEP) = NPCList(NPCIndex).ModStat(SID.MaxEP)
    If NPCList(NPCIndex).BaseStat(SID.MinEP) < 0 Then NPCList(NPCIndex).BaseStat(SID.MinEP) = 0
 
    'Check to update health percentage client-side
    If NPCList(NPCIndex).BaseStat(SID.MinEP) > 0 Then
        EPB = CByte((NPCList(NPCIndex).BaseStat(SID.MinEP) / NPCList(NPCIndex).ModStat(SID.MaxEP)) * 100)
        If EPA <> EPB Then
            ConBuf.PreAllocate 4
            ConBuf.Put_Byte DataCode.Server_CharEP
            ConBuf.Put_Byte EPB
            ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
            Data_Send ToMap, NPCIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_StatPercent
        End If
    Else
        ConBuf.PreAllocate 4
        ConBuf.Put_Byte DataCode.Server_CharEP
        ConBuf.Put_Byte 0
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        Data_Send ToMap, NPCIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_StatPercent
    End If
 
End Sub

Private Sub NPC_ChangeChar(ByVal sndRoute As Byte, ByVal sndIndex As Integer, ByVal NPCIndex As Integer, Optional ByVal Body As Integer = -1, Optional ByVal Head As Integer = -1, Optional ByVal Weapon As Integer = -1, Optional ByVal Hair As Integer = -1, Optional ByVal Wings As Integer = -1)

'*****************************************************************
'Changes a NPC char's head,body and heading
'*****************************************************************
Dim ChangeFlags As Byte
Dim FlagSizes As Byte

    Log "Call NPC_ChangeChar(" & sndRoute & "," & sndIndex & "," & NPCIndex & "," & Body & "," & Head & "," & Weapon & "," & Hair & "," & Wings & ")", CodeTracker  '//\\LOGLINE//\\

    'Check for a valid NPC
    If NPCIndex <= 0 Then
        Log "NPC_ChangeChar: NPCIndex <= 0 - aborting", CriticalError '//\\LOGLINE//\\
        Exit Sub
    End If
    If NPCIndex > LastNPC Then
        Log "NPC_ChangeChar: NPCIndex > LastNPC - aborting", CriticalError '//\\LOGLINE//\\
        Exit Sub
    End If
    
    'Check for changed values
    With NPCList(NPCIndex).Char
        If Body > -1 Then
            If .Body <> Body Then
                .Body = Body
                ChangeFlags = ChangeFlags Or 1
                FlagSizes = FlagSizes + 2
            End If
        End If
        If Head > -1 Then
            If .Head <> Head Then
                .Head = Head
                ChangeFlags = ChangeFlags Or 2
                FlagSizes = FlagSizes + 2
            End If
        End If
        If Weapon > -1 Then
            If .Weapon <> Weapon Then
                .Weapon = Weapon
                ChangeFlags = ChangeFlags Or 4
                FlagSizes = FlagSizes + 2
            End If
        End If
        If Hair > -1 Then
            If .Hair <> Hair Then
                .Hair = Hair
                ChangeFlags = ChangeFlags Or 8
                FlagSizes = FlagSizes + 2
            End If
        End If
        If Wings > -1 Then
            If .Wings <> Wings Then
                .Wings = Wings
                ChangeFlags = ChangeFlags Or 16
                FlagSizes = FlagSizes + 2
            End If
        End If
    End With
    
    'Make sure there is a packet to send
    If ChangeFlags = 0 Then Exit Sub
    
    'Create the packet
    ConBuf.PreAllocate 4 + FlagSizes
    ConBuf.Put_Byte DataCode.Server_ChangeChar
    ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
    ConBuf.Put_Byte ChangeFlags
    If ChangeFlags And 1 Then ConBuf.Put_Integer Body
    If ChangeFlags And 2 Then ConBuf.Put_Integer Head
    If ChangeFlags And 4 Then ConBuf.Put_Integer Weapon
    If ChangeFlags And 8 Then ConBuf.Put_Integer Hair
    If ChangeFlags And 16 Then ConBuf.Put_Integer Wings
    Data_Send sndRoute, sndIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_ChangeChar

End Sub

Public Sub NPC_Close(ByVal NPCIndex As Integer, Optional ByVal CleanArray As Byte = 1)

'*****************************************************************
'Closes a NPC
'*****************************************************************

    Log "Call NPC_Close(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\

    'Close down the NPC
    NPCList(NPCIndex).Flags.NPCActive = 0
    CharList(NPCList(NPCIndex).Char.CharIndex).Index = 0
    CharList(NPCList(NPCIndex).Char.CharIndex).CharType = 0

    'Clean up the NPC array
    If NPCIndex = LastNPC Then
        If CleanArray Then NPC_CleanArray
    End If
    
    'Update number of NPCs
    If NumNPCs <> 0 Then NumNPCs = NumNPCs - 1
    Log "NPC_Close: NumNPCs = " & NumNPCs, CodeTracker '//\\LOGLINE//\\

End Sub

Public Sub NPC_CleanArray()

'*****************************************************************
'Cleans the NPCList array to free memory
'*****************************************************************
Dim t As Integer    'Holds the unaltered value of LastNPC
    
    'Prevent crashing
    If LastNPC = 0 Then
        Erase NPCList
        Exit Sub
    End If

    'Store the LastNPC value for comparison later
    t = LastNPC

    'Loop through the NPCs from the last NPC backwards to find the number of slots we can clear
    Do Until NPCList(LastNPC).Flags.NPCActive = 1
        LastNPC = LastNPC - 1
        If LastNPC = 0 Then Exit Do
    Loop
    
    'Check if the LastNPC value has changed
    If t <> LastNPC Then
    
        'Resize the array (or erase)
        If LastNPC <> 0 Then
            ReDim Preserve NPCList(1 To LastNPC)
        Else
            Erase NPCList()
        End If
        
    End If

End Sub

Public Sub NPC_Heal(ByVal NPCIndex As Integer, ByVal Value As Long)
 
'*****************************************************************
'Raise a NPC's HP - ONLY USE THIS SUB TO RAISE NPC HP
'*****************************************************************
Dim HPA As Byte
Dim HPB As Byte
 
    'Get the pre-healing percentage
    HPA = CByte((NPCList(NPCIndex).BaseStat(SID.MinHP) / NPCList(NPCIndex).ModStat(SID.MaxHP)) * 100)
 
    'Raise the HP
    NPCList(NPCIndex).BaseStat(SID.MinHP) = NPCList(NPCIndex).BaseStat(SID.MinHP) + Value
 
    'Don't go over the limit
    If NPCList(NPCIndex).BaseStat(SID.MinHP) > NPCList(NPCIndex).ModStat(SID.MaxHP) Then NPCList(NPCIndex).BaseStat(SID.MinHP) = NPCList(NPCIndex).ModStat(SID.MaxHP)
 
    'Check to update health percentage client-side
    If NPCList(NPCIndex).BaseStat(SID.MinHP) > 0 Then
        HPB = CByte((NPCList(NPCIndex).BaseStat(SID.MinHP) / NPCList(NPCIndex).ModStat(SID.MaxHP)) * 100)
        If HPA <> HPB Then
            ConBuf.PreAllocate 4
            ConBuf.Put_Byte DataCode.Server_CharHP
            ConBuf.Put_Byte HPB
            ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
            Data_Send ToMap, NPCIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_StatPercent
        End If
    Else
        ConBuf.PreAllocate 4
        ConBuf.Put_Byte DataCode.Server_CharHP
        ConBuf.Put_Byte 0
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        Data_Send ToMap, NPCIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_StatPercent
    End If
 
End Sub

Public Sub NPC_Damage(ByVal NPCIndex As Integer, ByVal UserIndex As Integer, ByVal Damage As Long, Optional ByVal AttackerCharIndex As Integer = 0, Optional ByVal SendDamage As Boolean = True)

'*****************************************************************
'Do damage to a NPC - ONLY USE THIS SUB TO HURT NPCS
'*****************************************************************
Dim DamageB As Long
Dim GiveEXP As Long
Dim NewSlot As Byte
Dim NewX As Byte
Dim NewY As Byte
Dim HPA As Byte         'HP percentage before reducing hp
Dim HPB As Byte         'HP percentage after reducing hp
Dim i As Integer

    Log "Call NPC_Damage(" & NPCIndex & "," & UserIndex & "," & Damage & ")", CodeTracker  '//\\LOGLINE//\\
    
    'Clear the replenish value
    NPCList(NPCIndex).Counters.ReplenishCounter = timeGetTime

    'Check if the NPC can be attacked
    If NPCList(NPCIndex).Attackable = 0 Then
        Log "NPC_Damage: Attackable = 0 - aborting", CodeTracker '//\\LOGLINE//\\
        Exit Sub
    End If

    'If NPC has no health, they can not be attacked
    If NPCList(NPCIndex).ModStat(SID.MaxHP) = 0 Then
        Log "NPC_Damage: ModStat MaxHP = 0 - aborting", CodeTracker '//\\LOGLINE//\\
        Exit Sub
    End If
    If NPCList(NPCIndex).BaseStat(SID.MaxHP) = 0 Then
        Log "NPC_Damage: BaseStat MaxHP = 0 - aborting", CodeTracker '//\\LOGLINE//\\
        Exit Sub
    End If

    'Make sure the NPC isn't the user's slave (don't damage your slaves)
    If UserIndex > 0 Then
        If NPCList(NPCIndex).OwnerIndex = UserIndex Then Exit Sub
    End If
    
    'Display the damage on the client screen
    If SendDamage Then
        ConBuf.PreAllocate 5
        ConBuf.Put_Byte DataCode.Server_SetCharDamage
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        ConBuf.Put_Integer Damage
        Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map
    End If
    
    'Check for damage for the rest of the routine
    If Damage <= 0 Then Exit Sub
    
    'Get the pre-damage percentage
    HPA = CByte((NPCList(NPCIndex).BaseStat(SID.MinHP) / NPCList(NPCIndex).ModStat(SID.MaxHP)) * 100)
    
    'Calculate the NPC's GiveEXP
    With NPCList(NPCIndex)
        DamageB = Damage
        If Damage > .BaseStat(SID.MinHP) Then Damage = .BaseStat(SID.MinHP)
        GiveEXP = 1 + ((Damage / .ModStat(SID.MaxHP)) * .BaseStat(SID.EXP))
    End With
    
    'Lower the NPC's life
    NPCList(NPCIndex).BaseStat(SID.MinHP) = NPCList(NPCIndex).BaseStat(SID.MinHP) - Damage
    
    'Check to update health percentage client-side
    If NPCList(NPCIndex).BaseStat(SID.MinHP) > 0 Then
        HPB = CByte((NPCList(NPCIndex).BaseStat(SID.MinHP) / NPCList(NPCIndex).ModStat(SID.MaxHP)) * 100)
        If HPA <> HPB Then
            ConBuf.PreAllocate 4
            ConBuf.Put_Byte DataCode.Server_CharHP
            ConBuf.Put_Byte HPB
            ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
            Data_Send ToMap, NPCIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_StatPercent
        End If
    Else
        ConBuf.PreAllocate 4
        ConBuf.Put_Byte DataCode.Server_CharHP
        ConBuf.Put_Byte 0
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        Data_Send ToMap, NPCIndex, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map, PP_StatPercent
    End If
    
    'Give the user points for the damage (or split it through the group)
    If UserIndex > 0 Then
        If UserList(UserIndex).GroupIndex > 0 Then
            Group_SplitEXP UserIndex, UserList(UserIndex).GroupIndex, GiveEXP
        Else
            User_RaiseExp UserIndex, GiveEXP
        End If
    Else
        'The user's slave attacked the NPC, give the EXP to the user (or the user's group)
        If AttackerCharIndex > 0 Then
            If CharList(AttackerCharIndex).CharType = CharType_NPC Then
                If NPCList(CharList(AttackerCharIndex).Index).OwnerIndex > 0 Then
                    i = NPCList(CharList(AttackerCharIndex).Index).OwnerIndex
                    If i > 0 Then
                        If i <= LastUser Then
                            If UserList(i).GroupIndex > 0 Then
                                Group_SplitEXP i, UserList(UserIndex).GroupIndex, GiveEXP
                            Else
                                User_RaiseExp i, GiveEXP
                            End If
                        End If
                    End If
                End If
            End If
        End If
    End If
    
    'Check if the NPC died
    If NPCList(NPCIndex).BaseStat(SID.MinHP) <= 0 Then
        Log "NPC_Damage: NPC killed", CodeTracker '//\\LOGLINE//\\
    
        'If the NPC was killed by a user
        If UserIndex > 0 Then
        
            Log "NPC_Damage: It was a user who killed the NPC", CodeTracker '//\\LOGLINE//\\
            
            'Check for a reaver
            If UserList(UserIndex).Class And ClassID.Reaver Then
            
                'Increase rage
                User_AddRage UserIndex

            End If
            
            'Blood lust
            User_Bloodlust UserIndex, NPCIndex, Damage
            
            'Check on quests
            For i = 1 To MaxQuests
                If UserList(UserIndex).Quest(i) > 0 Then
                    If QuestData(UserList(UserIndex).Quest(i)).FinishReqNPC = NPCList(NPCIndex).NPCNumber Then
                        Log "NPC_Damage: User killed a NPC required for a quest", CodeTracker '//\\LOGLINE//\\

                        'User must kill at least one more of the NPC
                        If UserList(UserIndex).QuestStatus(i).NPCKills < QuestData(UserList(UserIndex).Quest(i)).FinishReqNPCAmount Then
                            UserList(UserIndex).QuestStatus(i).NPCKills = UserList(UserIndex).QuestStatus(i).NPCKills + 1
                            ConBuf.PreAllocate 7 + Len(NPCName(QuestData(UserList(UserIndex).Quest(i)).FinishReqNPC))
                            ConBuf.Put_Byte DataCode.Server_Message
                            ConBuf.Put_Byte 74
                            ConBuf.Put_Integer UserList(UserIndex).QuestStatus(i).NPCKills
                            ConBuf.Put_Integer QuestData(UserList(UserIndex).Quest(i)).FinishReqNPCAmount
                            ConBuf.Put_String NPCName(QuestData(UserList(UserIndex).Quest(i)).FinishReqNPC)
                            Data_Send ToIndex, UserIndex, ConBuf.Get_Buffer
                        End If

                    End If
                End If
            Next i
            
            'Give the user the gold (or if in a group, give it to the whole group)
            If UserList(UserIndex).GroupIndex > 0 Then
                Group_SplitGold UserIndex, UserList(UserIndex).GroupIndex, NPCList(NPCIndex).GiveGLD
            Else
                UserList(UserIndex).Stats.BaseStat(SID.Gold) = UserList(UserIndex).Stats.BaseStat(SID.Gold) + NPCList(NPCIndex).GiveGLD
            End If

        'If the NPC was attacked by a NPC slave to a user
        Else
            If AttackerCharIndex > 0 Then
                If CharList(AttackerCharIndex).CharType = CharType_NPC Then
                    If NPCList(CharList(AttackerCharIndex).Index).OwnerIndex > 0 Then
                        'The NPC is owned by a user, so give the EXP to the user (or group) like above
                        i = NPCList(CharList(AttackerCharIndex).Index).OwnerIndex
                        If i > 0 Then
                            If i <= LastUser Then
                                If UserList(i).GroupIndex > 0 Then
                                    Group_SplitGold i, UserList(i).GroupIndex, NPCList(NPCIndex).GiveGLD
                                Else
                                    UserList(i).Stats.BaseStat(SID.Gold) = UserList(i).Stats.BaseStat(SID.Gold) + NPCList(NPCIndex).GiveGLD
                                End If
                            End If
                        End If
                    End If
                End If
            End If
        End If
    
        'Drop items
        If NPCList(NPCIndex).NumDropItems > 0 Then
            For i = 1 To NPCList(NPCIndex).NumDropItems
                If NPCList(NPCIndex).DropRate(i) > (Rnd * 100) Then
                    Log "NPC_Damage: Item dropped (" & NPCList(NPCIndex).DropRate(i) & ")", CodeTracker '//\\LOGLINE//\\
                    
                    'Get the closest available position to put the item
                    Obj_ClosestFreeSpot NPCList(NPCIndex).Pos.Map, NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y, NewX, NewY, NewSlot
                    
                    'Make sure the position is valid
                    If NewX = 0 Then
                        
                        'If this object is invalid, so will the rest of them be, so just skip them all :(
                        Log "NPC_Damage: No valid item drop spot found - current and following loot will not appear", CodeTracker '//\\LOGLINE//\\
                        Exit For
                    
                    End If
                
                    'Create the object
                    Obj_Make NPCList(NPCIndex).DropItems(i), NewSlot, NPCList(NPCIndex).Pos.Map, NewX, NewY
                
                End If
            Next i
        End If

        'Kill off the NPC
        NPC_Kill NPCIndex

    End If

End Sub

Sub NPC_EraseChar(ByVal NPCIndex As Integer, ByVal MakeBlood As Byte)

'*****************************************************************
'Erase a character
'*****************************************************************

    Log "Call NPC_EraseChar(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\

    'Remove from list
    CharList(NPCList(NPCIndex).Char.CharIndex).Index = 0
    CharList(NPCList(NPCIndex).Char.CharIndex).CharType = 0
    
    'Remove from map
    MapInfo(NPCList(NPCIndex).Pos.Map).Data(NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y).NPCIndex = 0

    'Send erase command to clients
    ConBuf.PreAllocate 3
    ConBuf.Put_Byte DataCode.Server_EraseChar
    ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
    ConBuf.Put_Byte MakeBlood
    Data_Send ToMap, 0, ConBuf.Get_Buffer, NPCList(NPCIndex).Pos.Map

    'Clear the variables
    NPCList(NPCIndex).Char.CharIndex = 0
    NPCList(NPCIndex).Flags.NPCAlive = 0

    'Set at the respawn spot
    NPCList(NPCIndex).Pos.Map = NPCList(NPCIndex).StartPos.Map
    NPCList(NPCIndex).Pos.X = NPCList(NPCIndex).StartPos.X
    NPCList(NPCIndex).Pos.Y = NPCList(NPCIndex).StartPos.Y

End Sub

Public Sub NPC_Kill(ByVal NPCIndex As Integer)

'*****************************************************************
'Kill a NPC
'*****************************************************************
Dim CharIndex As Integer
Dim Slot As Byte
Dim i As Byte

    Log "Call NPC_Kill(" & NPCIndex & ")", CodeTracker '//\\LOGLINE//\\
    
    'Un-grab
    If NPCList(NPCIndex).Flags.GrabbedBy > 0 Then
        CharIndex = NPCList(NPCIndex).Flags.GrabbedBy

        'Check the character type
        Select Case CharList(CharIndex).CharType
            Case CharType_PC
                Skill_Grab_PCtoNPC_Release CharList(CharIndex).Index, NPCIndex
        End Select
        
    End If
    
    'Clear the counters
    ZeroMemory NPCList(NPCIndex).Counters, LenB(NPCList(NPCIndex).Counters)
    NPCList(NPCIndex).Flags.RendedBy = 0
    
    'If thralled, remove them
    If NPCList(NPCIndex).Flags.Thralled Then
        NPCList(NPCIndex).Flags.NPCActive = 0
        
        'If they were bounded as a slave (summon) NPC, change the user's summon count
        If NPCList(NPCIndex).OwnerIndex > 0 Then

            With UserList(NPCList(NPCIndex).OwnerIndex)

                'Find what slot the NPC was occupying
                For i = 1 To .NumSlaves
                    If .SlaveNPCIndex(i) = NPCIndex Then Exit For
                Next i
                Slot = i
                
                'Clean up the array
                If .NumSlaves = 1 Then
                    Erase .SlaveNPCIndex
                ElseIf Slot = .NumSlaves Then
                    ReDim Preserve .SlaveNPCIndex(1 To Slot - 1)
                Else
                    For i = Slot To .NumSlaves - 1
                        .SlaveNPCIndex(i) = .SlaveNPCIndex(i + 1)
                    Next i
                End If
                .NumSlaves = .NumSlaves - 1
                
            End With
            
        End If
                
    Else
        'Set death time for respawn wait
        NPCList(NPCIndex).Counters.RespawnCounter = timeGetTime + NPCList(NPCIndex).RespawnWait
    End If
    
    'Set HP and EP back to 100%
    NPCList(NPCIndex).BaseStat(SID.MinHP) = NPCList(NPCIndex).ModStat(SID.MaxHP)
    NPCList(NPCIndex).BaseStat(SID.MinEP) = NPCList(NPCIndex).ModStat(SID.MaxEP)

    'Erase it from map
    NPC_EraseChar NPCIndex, 1

End Sub

Public Sub NPC_MakeChar(ByVal sndRoute As Byte, ByVal sndIndex As Integer, ByVal NPCIndex As Integer, ByVal Map As Integer, ByVal X As Integer, ByVal Y As Integer, Optional ByVal SendPacket As Boolean = True)
 
'*****************************************************************
'Makes and places a NPC character
'*****************************************************************
Dim SndHP As Byte
Dim SndEP As Byte
Dim Flags As Integer
Dim PacketSize As Long
 
    Log "Call NPC_MakeChar(" & sndRoute & "," & sndIndex & "," & NPCIndex & "," & Map & "," & X & "," & Y & ")", CodeTracker '//\\LOGLINE//\\
    
    'Check for a valid send index
    If sndRoute = ToIndex Then
        If UserList(sndIndex).Pos.Map <> Map Then Exit Sub
    End If
    
    'Place character on map
    MapInfo(Map).Data(X, Y).NPCIndex = NPCIndex
 
    'Set alive flag
    NPCList(NPCIndex).Flags.NPCAlive = 1
    
    'Default to idling
    NPCList(NPCIndex).Flags.IsIdling = 1
 
    'Set the hp/ep to send
    If NPCList(NPCIndex).ModStat(SID.MaxHP) > 0 Then SndHP = CByte((NPCList(NPCIndex).BaseStat(SID.MinHP) / NPCList(NPCIndex).ModStat(SID.MaxHP)) * 100)
    If NPCList(NPCIndex).ModStat(SID.MaxEP) > 0 Then SndEP = CByte((NPCList(NPCIndex).BaseStat(SID.MinEP) / NPCList(NPCIndex).ModStat(SID.MaxEP)) * 100)
 
    'NPCs wont be created with active spells
    ZeroMemory NPCList(NPCIndex).ActiveSkills, LenB(NPCList(NPCIndex).ActiveSkills)
 
    'Find out what information needs to be sent
    'Typical users do NOT need to worry about the packet cache system!
    'Caching will only work when sending to a single client
    If sndRoute = ToIndex Then
        If sndIndex > LastUser Or sndIndex < 1 Then Exit Sub
        PacketSize = 2
        With UserList(sndIndex).PacketCache.Server_MakeChar
            If NPCList(NPCIndex).Char.Body <> .Body Then
                Flags = Flags Or 1
                .Body = NPCList(NPCIndex).Char.Body
                PacketSize = PacketSize + 2
            End If
            If NPCList(NPCIndex).Char.Head <> .Head Then
                Flags = Flags Or 2
                .Head = NPCList(NPCIndex).Char.Head
                PacketSize = PacketSize + 2
            End If
            If NPCList(NPCIndex).Char.Heading <> .Heading Then
                Flags = Flags Or 4
                .Heading = NPCList(NPCIndex).Char.Heading
                PacketSize = PacketSize + 1
            End If
            If X <> .X Then
                Flags = Flags Or 8
                .X = X
                PacketSize = PacketSize + 1
            End If
            If Y <> .Y Then
                Flags = Flags Or 16
                .Y = Y
                PacketSize = PacketSize + 1
            End If
            If NPCList(NPCIndex).BaseStat(SID.Speed) <> .Speed Then
                Flags = Flags Or 32
                .Speed = NPCList(NPCIndex).BaseStat(SID.Speed)
                PacketSize = PacketSize + 1
            End If
            If NPCList(NPCIndex).Name <> .Name Then
                Flags = Flags Or 64
                .Name = NPCList(NPCIndex).Name
                PacketSize = PacketSize + 1 + Len(.Name)
            End If
            If NPCList(NPCIndex).Char.Weapon <> .Weapon Then
                Flags = Flags Or 128
                .Weapon = NPCList(NPCIndex).Char.Weapon
                PacketSize = PacketSize + 2
            End If
            If NPCList(NPCIndex).Char.Hair <> .Hair Then
                Flags = Flags Or 256
                .Hair = NPCList(NPCIndex).Char.Hair
                PacketSize = PacketSize + 2
            End If
            If NPCList(NPCIndex).Char.Wings <> .Wings Then
                Flags = Flags Or 512
                .Wings = NPCList(NPCIndex).Char.Wings
                PacketSize = PacketSize + 2
            End If
            If SndHP <> .HP Then
                Flags = Flags Or 1024
                .HP = SndHP
                PacketSize = PacketSize + 1
            End If
            If SndEP <> .EP Then
                Flags = Flags Or 2048
                .EP = SndEP
                PacketSize = PacketSize + 1
            End If
            If NPCList(NPCIndex).ChatID <> .ChatID Then
                Flags = Flags Or 4096
                .ChatID = NPCList(NPCIndex).ChatID
                PacketSize = PacketSize + 1
            End If
            If NPCList(NPCIndex).OwnerIndex > 0 Then
                If ClientCharType_Slave <> .CharType Then
                    Flags = Flags Or 8192
                    .CharType = ClientCharType_Slave
                    PacketSize = PacketSize + 1
                End If
            Else
                If ClientCharType_NPC <> .CharType Then
                    Flags = Flags Or 8192
                    .CharType = ClientCharType_NPC
                    PacketSize = PacketSize + 1
                End If
            End If
 
        End With
    Else
        PacketSize = 22 + Len(NPCList(NPCIndex).Name)
    End If
 
    'Send make character command to clients
    If SendPacket Then
        ConBuf.PreAllocate PacketSize
    Else
        ConBuf.Allocate PacketSize
    End If
 
    'Check whether to send cached or not
    'CACHED
    If sndRoute = ToIndex Then
        ConBuf.Put_Byte DataCode.Server_MakeCharCached
        ConBuf.Put_Integer Flags
        If Flags And 1 Then ConBuf.Put_Integer NPCList(NPCIndex).Char.Body
        If Flags And 2 Then ConBuf.Put_Integer NPCList(NPCIndex).Char.Head
        If Flags And 4 Then ConBuf.Put_Byte NPCList(NPCIndex).Char.Heading
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        If Flags And 8 Then ConBuf.Put_Byte X
        If Flags And 16 Then ConBuf.Put_Byte Y
        If Flags And 32 Then ConBuf.Put_Byte NPCList(NPCIndex).BaseStat(SID.Speed)   'We dont use modstat on speed since for one it may not have been updated
        If Flags And 64 Then ConBuf.Put_String NPCList(NPCIndex).Name                ' yet, along with theres nothing to mod the stat
        If Flags And 128 Then ConBuf.Put_Integer NPCList(NPCIndex).Char.Weapon
        If Flags And 256 Then ConBuf.Put_Integer NPCList(NPCIndex).Char.Hair
        If Flags And 512 Then ConBuf.Put_Integer NPCList(NPCIndex).Char.Wings
        If Flags And 1024 Then ConBuf.Put_Byte SndHP
        If Flags And 2048 Then ConBuf.Put_Byte SndEP
        If Flags And 4096 Then ConBuf.Put_Byte NPCList(NPCIndex).ChatID
        If NPCList(NPCIndex).OwnerIndex > 0 Then
            If Flags And 8192 Then ConBuf.Put_Byte ClientCharType_Slave
            ConBuf.Put_Integer UserList(NPCList(NPCIndex).OwnerIndex).Char.CharIndex
        Else
            If Flags And 8192 Then ConBuf.Put_Byte ClientCharType_NPC
        End If
 
    'NOT CACHED
    Else
        ConBuf.Put_Byte DataCode.Server_MakeChar
        ConBuf.Put_Integer NPCList(NPCIndex).Char.Body
        ConBuf.Put_Integer NPCList(NPCIndex).Char.Head
        ConBuf.Put_Byte NPCList(NPCIndex).Char.Heading
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        ConBuf.Put_Byte X
        ConBuf.Put_Byte Y
        ConBuf.Put_Byte NPCList(NPCIndex).BaseStat(SID.Speed)   'We dont use modstat on speed since for one it may not have been updated
        ConBuf.Put_String NPCList(NPCIndex).Name                ' yet, along with theres nothing to mod the stat
        ConBuf.Put_Integer NPCList(NPCIndex).Char.Weapon
        ConBuf.Put_Integer NPCList(NPCIndex).Char.Hair
        ConBuf.Put_Integer NPCList(NPCIndex).Char.Wings
        ConBuf.Put_Byte SndHP
        ConBuf.Put_Byte SndEP
        ConBuf.Put_Byte NPCList(NPCIndex).ChatID
        If NPCList(NPCIndex).OwnerIndex > 0 Then
            ConBuf.Put_Byte ClientCharType_Slave
            ConBuf.Put_Integer UserList(NPCList(NPCIndex).OwnerIndex).Char.CharIndex
        Else
            ConBuf.Put_Byte ClientCharType_NPC
        End If
    End If
    
    'Status icons
    If NPCList(NPCIndex).ActiveSkills.CrackArmorTime > 0 Then
        ConBuf.Allocate 4
        ConBuf.Put_Byte DataCode.Server_SetIcon
        ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
        ConBuf.Put_Byte IconID.CrackArmor
    End If

    'Send the NPC
    If SendPacket Then Data_Send sndRoute, sndIndex, ConBuf.Get_Buffer, Map
 
End Sub

Public Function NPC_MoveChar(ByVal NPCIndex As Integer, ByVal nHeading As Byte, Optional ByVal UpdatePos As Boolean = True) As Byte

'*****************************************************************
'Moves a NPC from one tile to another
'*****************************************************************
Dim nPos As WorldPos
Dim CharIndex As Integer

    Log "Call NPC_MoveChar(" & NPCIndex & "," & nHeading & ")", CodeTracker '//\\LOGLINE//\\
    
    'Check if the NPC is being grabbed
    If NPCList(NPCIndex).Flags.GrabbedBy > 0 Then
        CharIndex = NPCList(NPCIndex).Flags.GrabbedBy

        'Confirm the one grabbing the NPC is actually grabbing them
        Select Case CharList(CharIndex).CharType
            Case CharType_PC
            
                'For some reason, the grabbing is only bound one way, so release it
                If UserList(CharList(CharIndex).Index).Flags.GrabChar <> NPCList(NPCIndex).Char.CharIndex Then
                    Skill_Grab_PCtoNPC_Release CharList(CharIndex).Index, NPCIndex
                End If
                
        End Select
        
        'The NPC is being grabbed, so no move for you!
        If NPCList(NPCIndex).Flags.GrabbedBy > 0 Then Exit Function
    
    End If
    
    'Move
    nPos = NPCList(NPCIndex).Pos
    Server_HeadToPos nHeading, nPos

    'Move if legal pos
    If Server_LegalPos(NPCList(NPCIndex).Pos.Map, nPos.X, nPos.Y, nHeading, True) Then

        'Set the move delay (we set the lag buffer to 0 since NPCs don't lag)
        NPCList(NPCIndex).Counters.ActionDelay = timeGetTime + Server_WalkTimePerTile(NPCList(NPCIndex).ModStat(SID.Speed))

        'Send the movement update packet
        If UpdatePos Then
            ConBuf.PreAllocate 6
            ConBuf.Put_Byte DataCode.Server_MoveChar
            ConBuf.Put_Integer NPCList(NPCIndex).Char.CharIndex
            ConBuf.Put_Byte nPos.X
            ConBuf.Put_Byte nPos.Y
            ConBuf.Put_Byte nHeading
            Data_Send ToNPCMove, NPCIndex, ConBuf.Get_Buffer
        End If
        
        'Update map and user pos
        MapInfo(NPCList(NPCIndex).Pos.Map).Data(NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y).NPCIndex = 0
        NPCList(NPCIndex).Pos = nPos
        NPCList(NPCIndex).Char.Heading = nHeading
        NPCList(NPCIndex).Char.HeadHeading = nHeading
        MapInfo(NPCList(NPCIndex).Pos.Map).Data(NPCList(NPCIndex).Pos.X, NPCList(NPCIndex).Pos.Y).NPCIndex = NPCIndex
        
        'NPC moved, return the flag
        NPC_MoveChar = 1

    End If
    
    Log "Rtrn NPC_MoveChar = " & NPC_MoveChar, CodeTracker '//\\LOGLINE//\\

End Function

Public Function NPC_NextOpen() As Integer

'*****************************************************************
'Finds the next open NPC Index in NPCList
'*****************************************************************

Dim LoopC As Long

    Log "Call NPC_NextOpen", CodeTracker '//\\LOGLINE//\\

    Do
        LoopC = LoopC + 1
        If LoopC > LastNPC Then
            LoopC = LastNPC + 1
            Exit Do
        End If
        If LoopC > MaxNPCs Then
            LoopC = 0
            Exit Do
        End If
    Loop While NPCList(LoopC).Flags.NPCActive = 1

    NPC_NextOpen = LoopC
    
    Log "Rtrn NPC_NextOpen = " & NPC_NextOpen, CodeTracker '//\\LOGLINE//\\

End Function

Public Sub NPC_Spawn(ByVal NPCIndex As Integer, Optional ByVal BypassUpdate As Byte = 0)

'*****************************************************************
'Places a NPC that has been Opened
'*****************************************************************
Dim TempPos As WorldPos
Dim CharIndex As Integer

    Log "Call NPC_Spawn(" & NPCIndex & "," & BypassUpdate & ")", CodeTracker '//\\LOGLINE//\\

    'Give it a char index if needed
    If NPCList(NPCIndex).Char.CharIndex = 0 Then
        CharIndex = Server_NextOpenCharIndex
        NPCList(NPCIndex).Char.CharIndex = CharIndex
        CharList(CharIndex).Index = NPCIndex
        CharList(CharIndex).CharType = CharType_NPC
    End If

    'Find a place to put npc
    Server_ClosestLegalPos NPCList(NPCIndex).StartPos, TempPos
    If Not Server_LegalPos(TempPos.Map, TempPos.X, TempPos.Y, 0) Then
        Log "NPC_Spawn: No legal pos found", CodeTracker '//\\LOGLINE//\\
        Exit Sub
    End If

    'Set vars
    NPCList(NPCIndex).Pos = TempPos
    NPCList(NPCIndex).Flags.NPCAlive = 1
    
    'Default to idling
    NPCList(NPCIndex).Flags.IsIdling = 1

    'Make NPC Char
    If BypassUpdate = 0 Then
        If MapInfo(TempPos.Map).NumUsers > 0 Then
            NPC_MakeChar ToMap, MapUsers(TempPos.Map).Index(1), NPCIndex, TempPos.Map, TempPos.X, TempPos.Y
        End If
    End If
    
End Sub
